package ru.urfc.portal.developer_tasks;

import java.io.IOException;
import java.io.PrintStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

import javax.annotation.Resource;
import javax.ejb.EJB;
import javax.ejb.SessionContext;
import javax.ejb.Stateless;
import javax.ejb.TransactionManagement;
import javax.ejb.TransactionManagementType;
import javax.servlet.http.HttpServletResponse;
import javax.transaction.UserTransaction;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;

import junit.framework.TestSuite;
import junit.textui.TestRunner;
import ru.urfc.portal.dao.BasePersistence;
import ru.urfc.portal.developer_tasks.Initializer.SysObjects;
import ru.urfc.portal.developer_tasks.ejb_tests.AccountingManagerTestEjb;
import ru.urfc.portal.developer_tasks.ejb_tests.AttachedFileDaoTestEjb;
import ru.urfc.portal.developer_tasks.ejb_tests.BalanceDaoTestEjb;
import ru.urfc.portal.developer_tasks.ejb_tests.BalanceOperationsAgregatorTestEjb;
import ru.urfc.portal.developer_tasks.ejb_tests.BasePersistenceTestEjb;
import ru.urfc.portal.developer_tasks.ejb_tests.ClientManagerTestEjb;
import ru.urfc.portal.developer_tasks.ejb_tests.ClientNotifificationSettingsTestEjb;
import ru.urfc.portal.developer_tasks.ejb_tests.ClientsSynchronizerTestEjb;
import ru.urfc.portal.developer_tasks.ejb_tests.DeviceManagerTestEjb;
import ru.urfc.portal.developer_tasks.ejb_tests.NetManagerTestEjb;
import ru.urfc.portal.developer_tasks.ejb_tests.RadioDaoTestEjb;
import ru.urfc.portal.developer_tasks.ejb_tests.TmpContragentDaoTestEjb;

@TransactionManagement(TransactionManagementType.BEAN)
@Stateless
@Produces({MediaType.TEXT_PLAIN})
@Path("tasks")
//TODO Remove it in production!!!
public class DeveloperTaskService {

	@Resource UserTransaction userTransaction;
	@Resource SessionContext context;
	@EJB Initializer initializer;
	@EJB BasePersistence basePersistence; // as example if EJB name is unknown 

	Class<?>[] ejbTestClasses = {
			BasePersistenceTestEjb.class,
			ClientManagerTestEjb.class,
			DeviceManagerTestEjb.class,
			NetManagerTestEjb.class,
			RadioDaoTestEjb.class,
			AccountingManagerTestEjb.class,
			BalanceDaoTestEjb.class,
			BalanceOperationsAgregatorTestEjb.class,
			TmpContragentDaoTestEjb.class,
			ClientsSynchronizerTestEjb.class,
			ClientNotifificationSettingsTestEjb.class,
			AttachedFileDaoTestEjb.class
	};

	private MapAndAnnotation[] pairs = {
			new MapAndAnnotation(new HashMap<Class<?>, Object>(), EJB.class),
			new MapAndAnnotation(new HashMap<Class<?>, Object>(), Resource.class)};

	@POST
	@Path("ejb-test")
	public String ejbTest(String value, @Context HttpServletResponse response) throws IOException {
		if (!("value=0ASpB1kHqheQdZGhiOTg5a3JfNzFyejh2dmJnOA".equals(value))) {
			return returnOk(value);
		}
		initEjbsAndResourcesMaps();
		TestRunner testRunner = new TestRunner(new PrintStream(response.getOutputStream()));
		TestSuite testSuite = new TestSuite();
		for (Class<?> testClass: ejbTestClasses) {
			testSuite.addTest(new EjbTestAdapter(testClass, this));
		}
		testRunner.doRun(testSuite);
		return "";
	}	
	
	static interface Callback {
		void withField(Field field, Map<Class<?>, Object> map) throws Exception;
	}
	
	static class MapAndAnnotation {
		Map<Class<?>, Object> map;
		Class<? extends Annotation> annotation;

		public MapAndAnnotation(Map<Class<?>, Object> map, Class<? extends Annotation> annotation) {
			this.map = map;
			this.annotation = annotation;
		}
	}

	private void initEjbsAndResourcesMaps() {
		visitClassFields(getClass(), pairs, new Callback() {
			@Override
			public void withField(Field field, Map<Class<?>, Object> map) throws Exception {
				map.put(field.getType(), field.get(DeveloperTaskService.this));
			}
		});
	}

	void initTest(final BaseTestEjb test) {
		visitClassFields(test.getClass(), pairs, new Callback() {
			@Override
			public void withField(Field field, Map<Class<?>, Object> map) throws Exception {
				Object value = map.get(field.getType());
				if (value == null) {
					try {
						value = context.lookup("java:module/" + field.getType().getSimpleName());
					} catch (RuntimeException e) {
						value = context.lookup("java:module/" + field.getType().getSimpleName() + "Impl");
					}
				}
				field.set(test, value);
			}
		});
	}

	private void visitClassFields(Class<?> klass, MapAndAnnotation[] pairs, Callback callback) {
		for (MapAndAnnotation pair: pairs) {
			visitClassFields(klass, pair, callback);
		}
	}

	private void visitClassFields(Class<?> klass, MapAndAnnotation pair, Callback callback) {
		if (!klass.getSuperclass().equals(Object.class)) {
			visitClassFields(klass.getSuperclass(), pair, callback);
		}
		Field[] fields = klass.getDeclaredFields();
		for (Field field: fields) {
			if (field.isAnnotationPresent(pair.annotation)) {
				boolean wasAccessible = field.isAccessible();
				try {
					field.setAccessible(true);
					callback.withField(field, pair.map);
				} catch (Exception e) {
					throw new RuntimeException(e.getMessage(), e);
				} finally {
					field.setAccessible(wasAccessible);
				}
			}
		}		
	}
	
	interface CallbackWithResult<T> {
		T doIt();
	}
	
	@POST
	@Path("init")
	public String initializeRdbms(String value) throws Throwable {
		if (!("v%D0%B0lu%D0%B5=0AiZdKS-7GPnGdExTOHl3d2pQdkh6R05IRHo3Ym9lVHc".equals(value))) {
			return returnOk(value);
		}
		final SysObjects urfcObjects = 
			doInTransaction(new CallbackWithResult<SysObjects>() {
			@Override
			public SysObjects doIt() {
				return initializer.createSpectr();
			}
		});
		doInTransaction(new CallbackWithResult<Object>() {
			@Override
			public Object doIt() {
				return initializer.createSys(urfcObjects);
			}
		});
		doInTransaction(new CallbackWithResult<Object>() {
			@Override
			public Object doIt() {
				return initializer.createEis(urfcObjects);
			}
		});
		return "Ok";
	}

	@POST
	@Path("production")
	public String initializeProductionRdbms(String value) throws Throwable {
		if (!("v%D0%B0lu%D0%B5=0AiZdKS-7GPnGdExTOHl3d2pQdkh6R05IRHo3Ym9lVHc".equals(value))) {
			return returnOk(value);
		}
		doInTransaction(new CallbackWithResult<Object>() {
			@Override
			public Object doIt() {
				return initializer.createSys(new SysObjects());
			}
		});
		return "Ok";
	}
	
	private <T> T doInTransaction(CallbackWithResult<T> callbackWithResult) throws Throwable {
		userTransaction.begin();
		try {
			T result = callbackWithResult.doIt();
			userTransaction.commit();
			return result;
		} catch  (Throwable e) {
			userTransaction.rollback();
			throw e;
		}
	}

	private String returnOk(String value) {
		return "Ok " + value.replaceAll(".*=", "value=") + " =)";
	}

}
